package br.senac.sp.persistence;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

public abstract class AbstractRelationalDBMapper<Entity, OID> extends AbstractPersistenceMapper<Entity, OID> {

    protected abstract Entity getObjectFromRecord(OID oid, ResultSet record) throws SQLException;

    protected abstract Map<String, Object> getInfoFromObject(Entity entity);

    protected abstract String getUniqueIdentifierArguments();

    private String tableName;

    public AbstractRelationalDBMapper(String tableName) {
        this.tableName = tableName;
    }

    @Override
    protected Entity getObjectFromStorage(OID oid) {
        try {
            ResultSet resultSet = getDatabaseRecord(oid);
            resultSet.next();
            return getObjectFromRecord(oid, resultSet);
        } catch (SQLException e) {
            return null;
            //throw new RuntimeException(e);
        }
    }

    @Override
    protected List<Entity> getObjectFromStorage() {
        ResultSet resultSet = getAllDatabaseRecords();
        return getObjectsFromRecords(resultSet);
    }

    @Override
    protected void insertNewEntityInStorage(Entity entity) {
        try {
            PreparedStatement statement = prepareInsertStatement(entity);
            statement.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void updateEntityInStorage(Entity entity) {
        try {
            PreparedStatement statement = prepareUpdateStatement(entity);
            statement.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void removeObjectFromStorage(Entity entity) {
        try {
            PreparedStatement statement = prepareDeleteStatement(getIdFromObject(entity));
            statement.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private ResultSet getAllDatabaseRecords() {
        try {
            return getDatabaseRecord(null);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private ResultSet getDatabaseRecord(OID oid) throws SQLException {
        Statement statement = currentConnection().createStatement();
        ResultSet result = statement.executeQuery(prepareSelectStatement(oid));
        return result;
    }

    private String prepareSelectStatement(OID oid) {
        StringBuilder sb = new StringBuilder();
        sb.append("SELECT * FROM ");
        sb.append(tableName);

        if (oid != null) {
            sb.append(" WHERE ");
            sb.append(getUniqueIdentifierArguments() + " = ");
            sb.append(oid);
        }
        return sb.toString();
    }

    private PreparedStatement prepareInsertStatement(Entity entity) throws SQLException {
        StringBuilder query = new StringBuilder();
        StringBuilder queryValues = new StringBuilder();
        PreparedStatement statement = null;

        Map<String, Object> values = getInfoFromObject(entity);

        query.append("INSERT INTO ");
        query.append(tableName);
        query.append(" (");

        Set<String> keys = values.keySet();
        for (Iterator<String> it = keys.iterator(); it.hasNext(); ) {
            String key = it.next();
            if (it.hasNext()) {
                query.append(key + ",");
                queryValues.append("?,");
            } else {
                query.append(key);
                queryValues.append("?");
            }
        }
        query.append(") VALUES (");
        query.append(queryValues.toString());
        query.append(")");

        statement = currentConnection().prepareStatement(query.toString());
        int i = 1;
        for (String string : keys) {
            statement.setObject(i, values.get(string));
            i++;
        }

        return statement;
    }

    private PreparedStatement prepareUpdateStatement(Entity entity) throws SQLException {
        StringBuilder query = new StringBuilder();
        PreparedStatement statement = null;

        Map<String, Object> values = getInfoFromObject(entity);

        query.append("UPDATE ");
        query.append(tableName);
        query.append(" SET ");

        Set<String> keys = values.keySet();
        for (Iterator<String> it = keys.iterator(); it.hasNext(); ) {
            String key = it.next();
            if (it.hasNext()) {
                query.append(key + "=?,");
            } else {
                query.append(key + "=?");
            }
        }

        query.append(" WHERE " + getUniqueIdentifierArguments() + "=?");

        statement = currentConnection().prepareStatement(query.toString());
        int i = 1;
        for (String string : keys) {
            statement.setObject(i, values.get(string));
            i++;
        }
        statement.setObject(i++, getIdFromObject(entity));
        return statement;
    }

    private PreparedStatement prepareDeleteStatement(OID id) throws SQLException {
        StringBuilder query = new StringBuilder();
        PreparedStatement statement = null;

        query.append("DELETE FROM ");
        query.append(tableName);
        query.append(" WHERE " + getUniqueIdentifierArguments() + "=?");

        statement = currentConnection().prepareStatement(query.toString());
        statement.setObject(1, id);
        return statement;
    }

    public String getTableName() {
        return tableName;
    }

    private List<Entity> getObjectsFromRecords(ResultSet records) {
        ArrayList<Entity> entities = new ArrayList<Entity>();
        try {
            while (records.next()) {
                OID id = (OID) records.getObject(getUniqueIdentifierArguments());
                entities.add(getObjectFromRecord(id, records));
            }
        } catch (SQLException e) {
            System.out.println(e);
        }
        return entities;
    }

    private Connection currentConnection() {
        IConnector connector = PersistenceFacade.getInstance().getConnector();

        if (connector == null) {
            throw new RuntimeException("Relational database connector was not assigned");
        } else if (!(connector instanceof RelationalDBConnector)) {
            throw new RuntimeException("Data storage is not a relational database");
        } else {
            return ((RelationalDBConnector) connector).getConnection();
        }

    }
}
